// Copyright (c) Lawrence Livermore National Security, LLC and other VisIt
// Project developers.  See the top-level LICENSE file for dates and other
// details.  No copyright assignment is required to contribute to VisIt.

#if !defined(_WIN32)
#ifdef AIX
#include <strings.h>
#endif
#include <unistd.h> // alarm
#include <signal.h>
#endif

#include <ConnectionGroup.h>
#include <DatabasePluginManager.h>
#include <DebugStream.h>
#include <MDServerApplication.h>
#include <MDServerConnection.h>
#include <LostConnectionException.h>
#include <Utility.h>

// Static member.
MDServerApplication *MDServerApplication::instance = NULL;

// ****************************************************************************
// Method: MDServerApplication::MDServerApplication
//
// Purpose: 
//   Constructor for the MDServerApplication class.
//
// Programmer: Brad Whitlock
// Creation:   Fri Nov 17 17:13:34 PST 2000
//
// Modifications:
//     Sean Ahern, Thu Feb 21 15:37:56 PST 2002
//     Added a default timeout of 0.  Without being set through SetTimeout,
//     no timeout will happen.
//   
// ****************************************************************************

MDServerApplication::MDServerApplication() : clients()
{
    pluginsLoaded = false;
    keepGoing = true;
    timeout = 0;
    databasePlugins = new DatabasePluginManager;

#if !defined(_WIN32)
    // Set up an alarm signal handler to exit gracefully.
    signal(SIGALRM, MDServerApplication::AlarmHandler);
#endif
}

// ****************************************************************************
// Method: MDServerApplication::~MDServerApplication
//
// Purpose: 
//   Destructor for the MDServerApplication class.
//
// Programmer: Brad Whitlock
// Creation:   Fri Nov 17 17:20:24 PST 2000
//
// Modifications:
//   Brad Whitlock, Fri Apr 18 14:58:09 PST 2003
//   I added code to delete the connections.
//
//   Brad Whitlock, Wed Jun 25 10:37:44 PDT 2008
//   Delete the plugin manager.
//
// ****************************************************************************

MDServerApplication::~MDServerApplication()
{
    instance = NULL;
    delete databasePlugins;

    for(size_t i = 0; i < clients.size(); ++i)
        delete clients[i];
}

// ****************************************************************************
// Method: MDServerApplication::Instance
//
// Purpose: 
//   Returns a pointer to the only instance of this class.
//
// Programmer: Brad Whitlock
// Creation:   Fri Nov 17 17:20:48 PST 2000
//
// Modifications:
//   
// ****************************************************************************

MDServerApplication *
MDServerApplication::Instance()
{
    if(instance == NULL)
        instance = new MDServerApplication;

    return instance;
}

// ****************************************************************************
// Method: MDServerApplication::AlarmHandler
//
// Purpose: 
//   Gracefully exits the MDServer if an SIGALRM signal was received.
//
// Programmer: Sean Ahern
// Creation:   Thu Feb 21 15:36:04 PST 2002
//
// Modifications:
//   
// ****************************************************************************

void
MDServerApplication::AlarmHandler(int signal)
{
    debug1 << "MDSERVER exited due to an inactivity timeout of "
           << MDServerApplication::Instance()->timeout << " minutes." << endl;
    exit(0); // HOOKS_IGNORE
}

// ****************************************************************************
// Method: MDServerApplication::AddConnection
//
// Purpose: 
//   Connects the MDServer to a remote process.
//
// Arguments:
//   argc : A pointer to the number of items in argv.
//   argv : A pointer to the argv argument list.
//
// Programmer: Brad Whitlock
// Creation:   Fri Nov 17 17:21:15 PST 2000
//
// Modifications:
//    Jeremy Meredith, Wed Dec 31 11:49:51 PST 2003
//    Added code to only trust the first client host seen.  This is to
//    fix issues with -guesshost when running through a NAT firewall.
//    See '4287 and '4288 for more details.
//
//    Brad Whitlock, Tue Jun 24 15:08:11 PDT 2008
//    Pass this to the MDServer connection.
//
// ****************************************************************************

void
MDServerApplication::AddConnection(int *argc, char **argv[])
{
    if (clientHost == "")
    {
        for (int i=0; i<*argc; i++)
        {
            if (!strcmp((*argv)[i], "-host"))
            {
                clientHost = (*argv)[i+1];
                break;
            }
        }
    }
    else
    {
        for (int i=0; i<*argc; i++)
        {
            if (!strcmp((*argv)[i], "-host"))
            {
                (*argv)[i+1] = CXX_strdup(clientHost.c_str());
                break;
            }
        }
    }

    MDServerConnection *newConnection = new MDServerConnection(this, argc, argv);
    clients.push_back(newConnection);

#ifdef DEBUG
    debug3 << "Added new client. total=" << clients.size() << endl;
#endif
}

// ****************************************************************************
// Method: MDServerApplication::Execute
//
// Purpose: 
//   This is the main event loop of the MDServer program. It executes a loop
//   that checks input on the MDServerConnections until one of them tells the
//   application to quit.
//
// Programmer: Brad Whitlock
// Creation:   Fri Nov 17 13:27:19 PST 2000
//
// Modifications:
//    Brad Whitlock, Thu Mar 15 12:34:02 PDT 2001
//    Added code to remove dead clients.
//
//    Brad Whitlock, Wed Apr 25 17:02:26 PST 2001
//    Modified code so the loop is not entered if there are zero clients.
//
//    Jeremy Meredith, Wed Oct 10 14:17:32 PDT 2001
//    Removed the sleep(1) and made it a blocking select.
//
//    Brad Whitlock, Mon Oct 22 18:33:37 PST 2001
//    Changed the exception keywords to macros.
//
//    Sean Ahern, Thu Feb 21 14:33:54 PST 2002
//    Added timeout support.
//
//    Brad Whitlock, Tue Mar 26 11:59:20 PDT 2002
//    I rewrote it so it uses a ConnectionGroup object to determine which
//    connections have input to be processed.
//
// ****************************************************************************

void
MDServerApplication::Execute()
{
    // Only go into the event loop if we have at least 1 client.
    keepGoing = (clients.size() > 0);

    // The application's main loop
    while(keepGoing)
    {
#if !defined(_WIN32)
        // Reset the alarm
        alarm(timeout*60);
#endif
        // Create a connection group that we will use to check if any
        // connections have input to be read.
        ConnectionGroup connGroup;
        for(size_t i = 0; i < clients.size(); ++i)
            connGroup.AddConnection(clients[i]->GetWriteConnection());

        // Check the connections for input that needs to be processed.
        if(connGroup.CheckInput())
        {
            // Test all of the clients' file descriptors to see if they
            // can be read.
            std::vector<int> deadList;
            for(int i = 0; i < (int)clients.size(); ++i)
            {
                if(connGroup.NeedsRead(i))
                {
#if !defined(_WIN32)
                    // We've got some input.  Turn off the alarm so we can't
                    // time out.
                    alarm(0);
#endif

                    TRY
                    {
                        // Process input.
                        clients[i]->ProcessInput();
                    }
                    CATCH(LostConnectionException)
                    {
                        // Add the current index to the dead list.
                        deadList.push_back(i);
                    }
                    ENDTRY
                }
            }

            // Disconnect dead connections or ones that quit.
            DisconnectDeadConnections(deadList);
        }
    }

    //
    // Go through the list of MDServerConnections and close them down
    // by deleting the MDServerConnection objects.
    //
    for(size_t i = 0; i < clients.size(); ++i)
        delete clients[i];
    clients.clear();
}

// ****************************************************************************
// Method: MDServerApplication::ExecuteDebug
//
// Purpose: 
//   Reads and prints the file list before exiting.
//
// Programmer: Brad Whitlock
// Creation:   Fri Apr 18 14:59:32 PST 2003
//
// Modifications:
//   Brad Whitlock, Thu Jul 29 12:23:34 PDT 2004
//   Assumes extra smart file grouping.
//
//   Brad Whitlock, Fri Feb 4 15:22:11 PST 2005
//   Changed how file grouping options are passed.
//
// ****************************************************************************

void
MDServerApplication::ExecuteDebug()
{
    // Get the filtered file list.
    GetFileListRPC::FileList files;
    clients[0]->SetFileGroupingOptions("*", true);
    clients[0]->GetFilteredFileList(files);
    // Print the file list to the debug logs.
    debug1 << "FILELIST = " << files << endl;
}

// ****************************************************************************
// Method: EngineApplication::DisconnectDeadConnections
//
// Purpose: 
//   Removes dead connections from the list of clients. This keeps the server
//   responsive if some of its clients fail to disconnect before they die.
//
// Arguments:
//   deadList : The list of dead clients.
//
// Programmer: Brad Whitlock
// Creation:   Thu Mar 15 12:27:16 PDT 2001
//
// Modifications:
//   
// ****************************************************************************

void
MDServerApplication::DisconnectDeadConnections(const std::vector<int> &deadList)
{
    bool deletedConnections = (deadList.size() > 0);
    MDServerConnectionVector newClientList;

    // Delete any connections in the dead list.
    for(size_t i = 0; i < deadList.size(); ++i)
    {
        // Delete the connection.
        delete clients[deadList[i]];
        clients[deadList[i]] = 0;
    }

    // Delete any connections whose quit event indicates they wanted
    // to quit.
    for(size_t i = 0; i < clients.size(); ++i)
    {
        if(clients[i] == 0)
            continue;
        
        if(!clients[i]->KeepGoing())
        {
            deletedConnections = true;

            // Delete the connection.
            delete clients[i];
            clients[i] = 0;
        }
    }
    
    // If there were dead connections or connections that quit, we need to
    // get rid of the gaps in the clients vector.
    if(deletedConnections)
    {
        // If the first client is not dead or quit, keep going.
        keepGoing = (clients[0] != 0);

        // Make the new client vector
        for(size_t i = 0; i < clients.size(); ++i)
        {
            if(clients[i] != 0)
                newClientList.push_back(clients[i]);
#ifdef DEBUG
            else
            {
                debug1 << "MDSERVER: Disconnected client " << i << endl;
            }
#endif
        }

        // Copy the modified list into the real list.
        clients = newClientList;
    }
}

// ****************************************************************************
// Method: MDServerApplication::InitializePlugins
//
// Purpose: 
//   Initialize the plugins by reading the plugin info.
//
// Programmer: Brad Whitlock
// Creation:   Tue Jun 24 15:15:05 PDT 2008
//
// Modifications:
//   
// ****************************************************************************

void
MDServerApplication::InitializePlugins()
{
    databasePlugins->Initialize(DatabasePluginManager::MDServer, false);
}

// ****************************************************************************
// Method: MDServerApplication::LoadPlugins
//
// Purpose: 
//   Loads the database plugins.
//
// Note:       Moved from MDServerConnection.
//
// Programmer: Brad Whitlock
// Creation:   Tue Jun 24 14:57:07 PDT 2008
//
// Modifications:
//   
// ****************************************************************************

void
MDServerApplication::LoadPlugins()
{
    if(!pluginsLoaded)
    {
        debug2 << "Loading plugins!" << endl;
        databasePlugins->LoadPluginsNow();
        pluginsLoaded = true;
    }
}

// ****************************************************************************
// Method: MDServerApplication::GetPluginInitializationErrors
//
// Purpose: 
//   Return the plugin initialization errors.
//
// Returns:    The plugin initialization error string.
//
// Programmer: Brad Whitlock
// Creation:   Tue Jun 24 15:04:56 PDT 2008
//
// Modifications:
//   
// ****************************************************************************

std::string
MDServerApplication::GetPluginInitializationErrors()
{
    return databasePlugins->GetPluginInitializationErrors();
}

// ****************************************************************************
// Method: MDServerApplication::GetDatabasePluginManager
//
// Purpose: 
//   Return the database plugin manager.
//
// Returns:    The database plugin manager.
//
// Programmer: Brad Whitlock
// Creation:   Tue Jun 24 15:05:18 PDT 2008
//
// Modifications:
//   
// ****************************************************************************

DatabasePluginManager *
MDServerApplication::GetDatabasePluginManager()
{
    return databasePlugins;
}
